/*
Copyright 2022 The Karmada Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package storage

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime"
	genericrequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/registry/rest"
	"k8s.io/klog/v2"

	searchapis "github.com/karmada-io/karmada/pkg/apis/search"
	clusterlister "github.com/karmada-io/karmada/pkg/generated/listers/cluster/v1alpha1"
	"github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager"
)

// SearchREST implements a RESTStorage for search resource.
type SearchREST struct {
	multiClusterInformerManager genericmanager.MultiClusterInformerManager
	clusterLister               clusterlister.ClusterLister
	restMapper                  meta.RESTMapper

	// add needed parameters here
}

var _ rest.Scoper = &SearchREST{}
var _ rest.Storage = &SearchREST{}
var _ rest.Connecter = &SearchREST{}
var _ rest.SingularNameProvider = &SearchREST{}

// NewSearchREST returns a RESTStorage object that will work against search.
func NewSearchREST(
	multiClusterInformerManager genericmanager.MultiClusterInformerManager,
	clusterLister clusterlister.ClusterLister,
	restMapper meta.RESTMapper) *SearchREST {
	return &SearchREST{
		multiClusterInformerManager: multiClusterInformerManager,
		clusterLister:               clusterLister,
		restMapper:                  restMapper,
	}
}

// New return empty Search object.
func (r *SearchREST) New() runtime.Object {
	return &searchapis.Search{}
}

// NamespaceScoped returns false because Search is not namespaced.
func (r *SearchREST) NamespaceScoped() bool {
	return false
}

// ConnectMethods returns the list of HTTP methods handled by Connect.
func (r *SearchREST) ConnectMethods() []string {
	return []string{"GET"}
}

// NewConnectOptions returns an empty options object that will be used to pass options to the Connect method.
func (r *SearchREST) NewConnectOptions() (runtime.Object, bool, string) {
	return nil, true, ""
}

// Connect returns a handler for search.
func (r *SearchREST) Connect(ctx context.Context, id string, _ runtime.Object, responder rest.Responder) (http.Handler, error) {
	info, ok := genericrequest.RequestInfoFrom(ctx)
	if !ok {
		return nil, fmt.Errorf("no RequestInfo found in the context")
	}

	if len(info.Parts) < 3 {
		return nil, fmt.Errorf("invalid requestInfo parts: %v", info.Parts)
	}

	// reqParts are slices split by the k8s-native URL that include
	// APIPrefix, APIGroup, APIVersion, Namespace and Resource.
	// For example, the whole request URL is /apis/search.karmada.io/v1alpha1/search/cache/api/v1/nodes
	// info.Parts is [search cache api v1 nodes], so reParts is [api v1 nodes]
	reqParts := info.Parts[2:]
	nativeResourceInfo, err := parseK8sNativeResourceInfo(reqParts)
	if err != nil {
		klog.Errorf("Failed to parse k8s native RequestInfo, err: %v", err)
		return nil, err
	}
	if !nativeResourceInfo.IsResourceRequest {
		return nil, fmt.Errorf("k8s native Request URL(%s) is not a resource request", strings.Join(reqParts, "/"))
	}

	switch id {
	case "cache":
		return r.newCacheHandler(nativeResourceInfo, responder)
	case "opensearch":
		return r.newOpenSearchHandler(nativeResourceInfo, responder)
	default:
		return nil, fmt.Errorf("connect with unrecognized search category %s", id)
	}
}

// Destroy cleans up its resources on shutdown.
func (r *SearchREST) Destroy() {
	// Given no underlying store, so we don't
	// need to destroy anything.
}

// GetSingularName returns singular name of resources
func (r *SearchREST) GetSingularName() string {
	return "search"
}
